using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace Sherwood.Imaging.Filters
{
    public class AddRoundCornersFilter : IFilter
    {
        public AddRoundCornersFilter(INumberProvider cornerRadius, IColorProvider cornerColor, IBooleanProvider topLeft, IBooleanProvider topRight, IBooleanProvider bottomLeft, IBooleanProvider bottomRight)
        {
            CornerRadius = cornerRadius;
            CornerColor = cornerColor;
            TopLeft = topLeft;
            TopRight = topRight;
            BottomLeft = bottomLeft;
            BottomRight = bottomRight;
        }

        public INumberProvider CornerRadius { get; set; }
        public IColorProvider CornerColor { get; set; }
        public IBooleanProvider TopLeft { get; set; }
        public IBooleanProvider TopRight { get; set; }
        public IBooleanProvider BottomLeft { get; set; }
        public IBooleanProvider BottomRight { get; set; }

        /// <exception cref="ArgumentNullException"><c>image</c> is null.</exception>
        public Image ApplyFilter(Image image)
        {
            if (image == null) throw new ArgumentNullException("image");

            const int resizeFactor = 4; // we draw the corners scaled to improve the aliasing quality

            var radius = (int) CornerRadius.GetNumber()*resizeFactor;
            var cornerColor = CornerColor.GetColor();

            var newImage = new Bitmap(image.Width*resizeFactor + radius,
                                      image.Height*resizeFactor + radius, PixelFormat.Format32bppArgb);
            using (var graphics = Graphics.FromImage(newImage))
            {
                graphics.CompositingQuality = CompositingQuality.HighQuality;
                graphics.SmoothingMode = SmoothingMode.HighQuality;
                graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;

                var distance = radius/2;
                var tmp = radius * 2 + distance * 2 + resizeFactor / 2;

                // first we create a circle which defines all the four round corners (height and width are CornerRadius * 2)
                // then we add a rectangle (CornerRadius * CornerRadius) to the corner we want to handle and subtract the right
                // part of the circle from the rectangle. After doing this (setting clipping) on all wanted corners, we apply
                // the corner color

                var circle = new GraphicsPath();
                circle.AddEllipse(distance, distance, radius*2, radius*2);

                graphics.SetClip(new Rectangle(0, 0, 0, 0));
                if (TopLeft.GetValue())
                {
                    graphics.SetClip(new Rectangle(0, 0, radius + distance, radius + distance), CombineMode.Union);
                    graphics.SetClip(circle, CombineMode.Exclude);
                }
                if (TopRight.GetValue())
                {
                    graphics.SetClip(
                        new Rectangle(newImage.Width - radius - distance, 0, radius + distance, radius + distance),
                        CombineMode.Union);
                    var region = new Region(circle);
                    region.Translate(newImage.Width - tmp, 0);
                    graphics.SetClip(region, CombineMode.Exclude);
                }
                if (BottomRight.GetValue())
                {
                    graphics.SetClip(
                        new Rectangle(newImage.Width - radius - distance, newImage.Height - radius - distance,
                                      radius + distance,
                                      radius + distance),
                        CombineMode.Union);
                    var region = new Region(circle);
                    region.Translate(newImage.Width - tmp,
                                     newImage.Height - tmp);
                    graphics.SetClip(region, CombineMode.Exclude);
                }
                if (BottomLeft.GetValue())
                {
                    graphics.SetClip(
                        new Rectangle(0, newImage.Height - radius - distance, radius + distance, radius + distance),
                        CombineMode.Union);
                    var region = new Region(circle);
                    region.Translate(0, newImage.Height - tmp);
                    graphics.SetClip(region, CombineMode.Exclude);
                }
                graphics.FillRectangle(new SolidBrush(cornerColor),
                                       distance - radius, distance - radius, newImage.Width + radius*2,
                                       newImage.Height + radius*2);

                using (var g2 = Graphics.FromImage(image))
                {
                    g2.CompositingQuality = CompositingQuality.HighQuality;
                    g2.SmoothingMode = SmoothingMode.HighQuality;
                    g2.InterpolationMode = InterpolationMode.HighQualityBilinear;

                    g2.DrawImage(newImage, new Rectangle(Point.Empty, image.Size),
                                 new Rectangle(distance, distance, newImage.Width - radius,
                                               newImage.Height - radius), GraphicsUnit.Pixel);
                    return image;
                }
            }
        }
    }
}